package comp

import (
	"archive/tar"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"
)

// _
const (
	MD5Empty = "a2e4822a98337283e39f7b60acf85ec9" // empty
	MD5Dir   = "736007832d2167baaae763fd3a3f3cf1" // dir

	DefaultDeCompressDir = "/tmp/comp-incr-difference/"
)

// GetSourcePaths 根据资源路径获取该资源下所有子资源的path和对应md5值
func (cf *CFile) GetSourcePaths() map[string]string {
	pm := map[string]string{} // path, file md5
	cf.DeCompressDir = DefaultDeCompressDir + fmt.Sprint(time.Now().Unix())

	if cf.IsCompressed() {
		pm = getCompressedPaths(cf.Path, cf.DeCompressDir)
	} else if cf.FileType == TypeDir {
		pm = getSourcePaths(cf.Path, cf.IgorePath)
	} else {
		pm[getRelativeKey(cf.Path, cf.IgorePath)] = Md5FromFile(cf.Path)
	}

	return pm
}

func getCompressedPaths(path string, deCompressDir string) map[string]string {
	pm := map[string]string{} // path, file md5

	srcFile, err := os.Open(path)
	if err != nil {
		return pm
	}
	defer srcFile.Close()

	gr, err := gzip.NewReader(srcFile)
	if err != nil {
		log.Println("getCompressedPaths gzip.NewReader error, err:", err)
		return pm
	}
	defer gr.Close()

	tr := tar.NewReader(gr)
	for {
		hdr, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				break
			} else {
				log.Println("getCompressedPaths tr.Next error, err:", err)
				return pm
			}
		}
		filename := deCompressDir + "/" + hdr.Name

		fi := hdr.FileInfo()
		if fi.IsDir() {
			os.MkdirAll(filename, 0755)
			continue
		}

		file, err := createFile(filename)
		if err != nil {
			log.Println("getCompressedPaths createFile error, err:", err)
			return pm
		}

		io.Copy(file, tr)
	}

	pm = getSourcePaths(deCompressDir, deCompressDir)
	return pm
}

func createFile(name string) (*os.File, error) {
	err := os.MkdirAll(string([]rune(name)[0:strings.LastIndex(name, "/")]), 0755)
	if err != nil {
		return nil, err
	}
	return os.Create(name)
}

// GetSourcePaths 根据资源路径获取该资源下所有子资源的path和对应md5值
func getSourcePaths(path, igorePath string) map[string]string {
	pm := map[string]string{} // path, file md5

	fileInfo, err := os.Stat(path)
	if err != nil {
		log.Println("os stat fail:", err.Error(), " path:", path)
		return nil
	}

	if !fileInfo.IsDir() {
		pm[getRelativeKey(path, igorePath)] = Md5FromFile(path)
		return pm
	}

	fis, err := ioutil.ReadDir(path)
	if err != nil {
		log.Println("read dir fail:", err)
		return nil
	}

	for _, fi := range fis {
		filename := path + "/" + fi.Name()
		fileInfo, err := os.Stat(filename)
		if err != nil {
			log.Println("os stat fail:", err.Error(), " path:", filename)
			return nil
		}

		if !fileInfo.IsDir() {
			pm[getRelativeKey(filename, igorePath)] = Md5FromFile(filename)
			continue
		}

		spm := getSourcePaths(filename, igorePath)
		for k, v := range spm {
			pm[k] = v // 直接设置，不考虑重复，理论上不会有重复
		}
	}

	return pm
}

func getRelativeKey(m, s string) string {
	if !strings.HasSuffix(s, "/") {
		s += "/"
	}
	return strings.Replace(m, s, "", -1)
}

func getAbsPath(base, suffix string) string {
	if strings.HasSuffix(base, "/") {
		return base + suffix
	}

	return base + "/" + suffix
}

// Md5FromFile 获取文件的md5值
func Md5FromFile(filename string) string {
	f, err := os.Open(filename)
	if err != nil {
		log.Println("os open fail, name:", filename, "err:", err.Error())
		return MD5Empty
	}
	defer f.Close()

	md5hash := md5.New()
	if _, err := io.Copy(md5hash, f); err != nil {
		log.Println("io copy fail, name:", filename, "err:", err.Error())
		return MD5Empty
	}

	return MD5(md5hash.Sum(nil))
}

// MD5 获取md5值
func MD5(body []byte) string {
	return fmt.Sprintf("%x", md5.Sum(body))
}
